package com.devshephard.jucks.SingleDemo;


/**
 * 懒汉式单例-双重检测锁模式(DCL),线程安全的
 */
public class LazyManDoubleCheck {

    //private static LazyManDoubleCheck lazyManlaDoubleCheck;
    private static volatile LazyManDoubleCheck lazyManlaDoubleCheck; //加入volatile关键字,防止指令重排的问题

    //单例模式必须构造器私有
    private LazyManDoubleCheck() {
        System.out.println(Thread.currentThread().getName() + "ok");  //多线程情况下输出一下当前线程,由于放在了构造器中,如果有多个则表明生成了多个对象
    }

    public static LazyManDoubleCheck getInstance(){
        if(lazyManlaDoubleCheck == null){ //当lazyMan对象为空,则表明没有生成到内存空间中
            synchronized (LazyManDoubleCheck.class){ //为类加锁,保证多线程下线程安全
                if(lazyManlaDoubleCheck == null){
                    /**
                     * 极端情况下,DCL还是会出现问题,由于以下代码并非原子性操作,可能会出现指令重排的问题.其底层操作步骤如下
                     * 1.分配内存空间
                     * 2.执行构造方法,初始化对象
                     * 3.把这个对象指向这个空间
                     * 其中,有可能会出现的执行步骤有 1->2->3 或 1->3->2
                     * 假设线程A,执行1->3->2步骤,这时候已经将对象指向内存空间,即 lazyManlaDoubleCheck不为空,
                     * 这时候线程B进入第18行代码的(第一个lazyManlaDoubleCheck是否为空的判断)判断,得出结论lazyManlaDoubleCheck对象不为空,则会返回空的lazyManlaDoubleCheck对象,发生问题.
                     * 解决方式为:声明 private static LazyManDoubleCheck lazyManlaDoubleCheck; 加入volatile关键字
                     */
                    lazyManlaDoubleCheck = new LazyManDoubleCheck();
                }
            }
        }
        return lazyManlaDoubleCheck;
    };

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {  //模拟多线程情况下线程不安全
            new Thread(()->{
                LazyManDoubleCheck.getInstance();
            }).start();
        }
    }

}
